/* Copyright 2009
 *
 * This program and the accompanying materials
 * are made available under the terms of the
 * Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at
 *
 * 		http://www.eclipse.org/legal/epl-v10.html
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * Contributors:
 * 	   IBM Corporation - initial API and implementation for JDT/DLTK
 *     Sean W. Quinn - initial adoption for use with PHP from various sources.
 */
package org.eclipse.php.internal.core;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.dltk.core.BufferChangedEvent;
import org.eclipse.dltk.core.CompletionRequestor;
import org.eclipse.dltk.core.IBuffer;
import org.eclipse.dltk.core.IBufferChangedListener;
import org.eclipse.dltk.core.IOpenable;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.WorkingCopyOwner;
import org.eclipse.dltk.internal.core.BufferManager;
import org.eclipse.dltk.internal.core.ModelManager;
import org.eclipse.dltk.internal.core.OpenableElementInfo;
import org.eclipse.php.core.IPHPElement;
import org.eclipse.php.core.IPHPModelStatusConstants;
import org.eclipse.php.core.IProgramUnit;
import org.eclipse.php.core.ITypeRoot;
import org.eclipse.php.core.PHPModelException;

/**
 * <p>
 * This class represents a PHP specific implementation of the
 * <code>Openable</code> class found in both Java's JDT and the Dynamic
 * Languages Toolkit (DLTK). Ideally we would employ the DLTK's
 * {@link org.eclipse.dltk.internal.core.Openable} class instead of this one,
 * but presently there are needs to acquire the Script project.
 * </p>
 *
 * @author Sean.Quinn
 * @deprecated use {@link org.eclipse.dltk.internal.core.Openable} instead
 * @see org.eclipse.dltk.internal.core.Openable
 * @see org.eclipse.jdt.internal.core.Openable
 */
@SuppressWarnings("restriction")
@Deprecated
public abstract class Openable extends PHPElement implements IOpenable, IBufferChangedListener {

	protected Openable(PHPElement parent) {
		super(parent);
	}

	/**
	 * The buffer associated with this element has changed. Registers this
	 * element as being out of synch with its buffer's contents. If the buffer
	 * has been closed, this element is set as NOT out of synch with the
	 * contents.
	 *
	 * @see IBufferChangedListener
	 */
	public void bufferChanged(BufferChangedEvent event) {
		// FIXME: Implement me!
		throw new RuntimeException("org.eclipse.php.internal.core.Openable#bufferChanged() not yet implemented.");
	}

	/**
	 * Builds this element's structure and properties in the given info object,
	 * based on this element's current contents (reuse buffer contents if this
	 * element has an open buffer, or resource contents if this element does not
	 * have an open buffer). Children are placed in the given newElements table
	 * (note, this element has already been placed in the newElements table).
	 * Returns true if successful, or false if an error is encountered while
	 * determining the structure of this element.
	 */
	protected abstract boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements,
			IResource underlyingResource) throws ModelException;

	/*
	 * Returns whether this element can be removed from the Java model cache to
	 * make space.
	 */
	public boolean canBeRemovedFromCache() {
		try {
			return !hasUnsavedChanges();
		}
		catch (ModelException e) {
			return false;
		}
	}

	/*
	 * Returns whether the buffer of this element can be removed from the Java
	 * model cache to make space.
	 */
	public boolean canBufferBeRemovedFromCache(IBuffer buffer) {
		return !buffer.hasUnsavedChanges();
	}

	/**
	 * Close the buffer associated with this element, if any.
	 */
	protected void closeBuffer() {
		if (!hasBuffer())
			return; // nothing to do
		IBuffer buffer = getBufferManager().getBuffer(this);
		if (buffer != null) {
			buffer.close();
			buffer.removeBufferChangedListener(this);
		}
	}

	/**
	 * This element is being closed. Do any necessary cleanup.
	 */
	@Override
	protected void closing(Object info) {
		closeBuffer();
	}

	protected void codeComplete(IProgramUnit unit, IProgramUnit unitToSkip, int position,
			CompletionRequestor requestor, WorkingCopyOwner owner, ITypeRoot typeRoot, IProgressMonitor monitor)
			throws ModelException {
		// FIXME: Implement me!
		throw new RuntimeException("org.eclipse.php.internal.core.Openable#codeComplete() not yet implemented.");
	}

	protected IPHPElement[] codeSelect(IProgramUnit cu, int offset, int length, WorkingCopyOwner owner)
			throws ModelException {
		// FIXME: Implement me!
		throw new RuntimeException("org.eclipse.php.internal.core.Openable#codeSelect() not yet implemented.");
	}

	/*
	 * Returns a new element info for this element.
	 */
	@Override
	protected Object createElementInfo() {
		return new OpenableElementInfo();
	}

	/**
	 * @see IPHPElement
	 */
	@Override
	public boolean exists() {
		// FIXME: Implement me!
		throw new RuntimeException("org.eclipse.php.internal.core.Openable#exists() not yet implemented.");
	}

	/**
	 * @return
	 * @throws ModelException
	 */
	public String findRecommendedLineSeparator() throws ModelException {
		// FIXME: Implement me!
		throw new RuntimeException(
				"org.eclipse.php.internal.core.Openable#findRecommendedLineSeparator() not yet implemented.");
	}

	/**
	 * @see org.eclipse.jdt.internal.core.Openable#generateInfos
	 * @see org.eclipse.dltk.internal.core.Openable#generateInfos
	 */
	@Override
	protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws PHPModelException {
		if (PHPModelCache.VERBOSE) {
			String element;
			switch (getElementType()) {
			case PHP_PROJECT:
				element = "project"; //$NON-NLS-1$
				break;
			case NAMESPACE_FRAGMENT_ROOT:
				element = "root"; //$NON-NLS-1$
				break;
			case NAMESPACE_FRAGMENT:
				element = "package"; //$NON-NLS-1$
				break;
			case SCRIPT_FILE:
				element = "script file"; //$NON-NLS-1$
				break;
			case PROGRAM_UNIT:
				element = "program unit"; //$NON-NLS-1$
				break;
			default:
				element = "element"; //$NON-NLS-1$
			}
			System.out.println(Thread.currentThread() + " OPENING " + element + " " + this.toStringWithAncestors()); //$NON-NLS-1$//$NON-NLS-2$
		}

		// FIXME: Implement me!
		throw new RuntimeException("org.eclipse.php.internal.core.Openable#generateInfos() not yet implemented.");
	}

	/**
	 * Note: a buffer with no unsaved changes can be closed by the Java Model
	 * since it has a finite number of buffers allowed open at one time. If this
	 * is the first time a request is being made for the buffer, an attempt is
	 * made to create and fill this element's buffer. If the buffer has been
	 * closed since it was first opened, the buffer is re-created.
	 *
	 * @see IOpenable
	 */
	public IBuffer getBuffer() throws ModelException {
		if (hasBuffer()) {
			// ensure element is open
			Object info = getElementInfo();
			IBuffer buffer = getBufferManager().getBuffer(this);
			if (buffer == null) {
				// try to (re)open a buffer
				buffer = openBuffer(null, info);
			}
			if (buffer instanceof NullBuffer) {
				return null;
			}
			return buffer;
		}
		else {
			return null;
		}
	}

	/**
	 * Returns the buffer manager for this element.
	 */
	protected BufferManager getBufferManager() {
		return BufferManager.getDefaultBufferManager();
	}

	/**
	 * Return my underlying resource. Elements that may not have a corresponding
	 * resource must override this method.
	 *
	 * @see IPHPElement
	 */
	public IResource getCorrespondingResource() throws ModelException {
		return getUnderlyingResource();
	}

	/*
	 * @see IPHPElement
	 */
	@Override
	public IOpenable getOpenable() {
		return this;
	}

	/**
	 * @see IPHPElement
	 */
	public IResource getUnderlyingResource() throws ModelException {
		IResource parentResource = this.parent.getUnderlyingResource();
		if (parentResource == null) {
			return null;
		}
		int type = parentResource.getType();
		if (type == IResource.FOLDER || type == IResource.PROJECT) {
			IContainer folder = (IContainer) parentResource;
			IResource resource = folder.findMember(getElementName());
			if (resource == null) {
				throw newNotPresentException();
			}
			else {
				return resource;
			}
		}
		else {
			return parentResource;
		}
	}

	/**
	 * Returns true if this element may have an associated source buffer,
	 * otherwise false. Subclasses must override as required.
	 */
	protected boolean hasBuffer() {
		return false;
	}

	/**
	 * @see IOpenable
	 */
	public boolean hasUnsavedChanges() throws ModelException {

		if (isReadOnly() || !isOpen()) {
			return false;
		}
		IBuffer buf = getBuffer();
		if (buf != null && buf.hasUnsavedChanges()) {
			return true;
		}
		// for package fragments, package fragment roots, and projects must check open buffers
		// to see if they have an child with unsaved changes
		int elementType = getElementType();
		if (elementType == NAMESPACE_FRAGMENT || elementType == NAMESPACE_FRAGMENT_ROOT || elementType == PHP_PROJECT
				|| elementType == PHP_MODEL) { // fix for 1FWNMHH
			Enumeration openBuffers = getBufferManager().getOpenBuffers();
			while (openBuffers.hasMoreElements()) {
				IBuffer buffer = (IBuffer) openBuffers.nextElement();
				if (buffer.hasUnsavedChanges()) {
					IPHPElement owner = (IPHPElement) buffer.getOwner();
					if (isAncestorOf(owner)) {
						return true;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Subclasses must override as required.
	 *
	 * @see IOpenable
	 */
	public boolean isConsistent() {
		return true;
	}

	/**
	 * @see IOpenable
	 */
	public boolean isOpen() {
		return ModelManager.getModelManager().getInfo(this) != null;
	}

	/**
	 * Returns true if this represents a source element. Openable source
	 * elements have an associated buffer created when they are opened.
	 */
	protected boolean isSourceElement() {
		return false;
	}

	/**
	 * @see IPHPElement
	 */
	public boolean isStructureKnown() throws ModelException {
		return ((OpenableElementInfo) getElementInfo()).isStructureKnown();
	}

	/**
	 * @see IOpenable
	 */
	public void makeConsistent(IProgressMonitor monitor) throws ModelException {
	// only compilation units can be inconsistent
	// other openables cannot be inconsistent so default is to do nothing
	}

	/**
	 * @see IOpenable
	 */
	public void open(IProgressMonitor pm) throws ModelException {
		getElementInfo(pm);
	}

	/**
	 * Opens a buffer on the contents of this element, and returns the buffer,
	 * or returns <code>null</code> if opening fails. By default, do nothing -
	 * subclasses that have buffers must override as required.
	 */
	protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws ModelException {
		return null;
	}

	@Override
	public IResource getResource() {
		NamespaceFragmentRoot root = getNamespaceFragmentRoot();
		if (root != null) {
			if (root.isExternal())
				return null;
			if (root.isArchive())
				return root.resource(root);
		}
		return resource(root);
	}

	@Override
	public IResource resource() {
		NamespaceFragmentRoot root = getNamespaceFragmentRoot();
		if (root != null && root.isArchive())
			return root.resource(root);
		return resource(root);
	}

	protected abstract IResource resource(NamespaceFragmentRoot root);

	/**
	 * Returns whether the corresponding resource or associated file exists
	 */
	protected boolean resourceExists(IResource underlyingResource) {
		return underlyingResource.isAccessible();
	}

	/**
	 * @see IOpenable
	 */
	public void save(IProgressMonitor pm, boolean force) throws ModelException {
		if (isReadOnly()) {
			throw new ModelException(new PHPModelStatus(IPHPModelStatusConstants.READ_ONLY, this));
		}
		IBuffer buf = getBuffer();
		if (buf != null) { // some Openables (like a JavaProject) don't have a buffer
			buf.save(pm, force);
			makeConsistent(pm); // update the element info of this element
		}
	}

	/**
	 * Find enclosing package fragment root if any
	 */
	public NamespaceFragmentRoot getNamespaceFragmentRoot() {
		return (NamespaceFragmentRoot) getAncestor(IPHPElement.NAMESPACE_FRAGMENT_ROOT);
	}

	/*
	 * Validates the existence of this openable. Returns a non ok status if it
	 * doesn't exist.
	 */
	abstract protected IStatus validateExistence(IResource underlyingResource);

	/*
	 * Opens the ancestors of this openable that are not yet opened, validating
	 * their existence.
	 */
	protected void openAncestors(HashMap newElements, IProgressMonitor monitor) throws ModelException {
		Openable openableParent = (Openable) getOpenableParent();
		if (openableParent != null && !openableParent.isOpen()) {
			openableParent.generateInfos(openableParent.createElementInfo(), newElements, monitor);
		}
	}
}